package ${filePackageName};

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import ${enumsPackage}.EnumCode;
import ${exceptionPackage}.BusinessException;
import ${redisPackage}.RedisUtils;
import ${tokenBeanPackage}.Custom;
import ${tokenBeanPackage}.TokenData;
import ${tokenBeanPackage}.TokenRedis;
import ${tokenBeanPackage}.UserToken;
import ${tokenConfigPackage}.TokenConfig;
import ${utilsPackage}.DESUtil;
import ${utilsPackage}.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Token 管理
 * @author ${projectAuthor}
 * @version 1.0
 * @date ${nowDate}
 */
@Service
public class TokenManager {

    private Logger logger = LoggerFactory.getLogger(TokenManager.class);

    @Resource
    private TokenConfig tokenConfig;

    @Resource
    private RedisUtils redisUtils;

    /**
     * 100年 永久有效
     */
    public static final long LAST_SECOND = 100*365*24*3600L;

    public boolean isMultiFlag() {
        return tokenConfig.getMultiFlag().equals("true");
    }

    /**
     * 设置token
     * @param sysCode
     * @param userId
     * @param expireSeconds
     */
    public String setTokenData(String sysCode, String userId,long expireSeconds) {
        String token = null;

        if (!isMultiFlag()){
            clearUserAllToken(sysCode,userId);
        }

        try {
            if(expireSeconds<=0){
                expireSeconds = LAST_SECOND;
            }
            String redisKey = sysCode + TokenRedis.REDIS_KEY_TOKEN;

            Custom custom = new Custom(tokenConfig.getTokenCustName(), tokenConfig.getTokenCustPass());
            TokenData tokenData = new TokenData();
            tokenData.setCustom(custom);
            tokenData.setTokenValue(userId);
            tokenData.setExpireTime(expireSeconds*1000);

            TokenRedis tokenRedis = new TokenRedis();
            tokenRedis.setData(tokenData);
            token = tokenRedis.getToken();

            String redisValue = DESUtil.getInstance(custom.getAccessKey()).encrypt(String.valueOf(tokenRedis.getEndTime())+
                    String.valueOf(expireSeconds));
            if(expireSeconds == LAST_SECOND){
                redisUtils.hset(redisKey,token,redisValue);
            }else{
                redisUtils.hset(redisKey,token,redisValue,expireSeconds);
            }
            setUserToken(sysCode,userId,token);
        }catch (Exception e){
            logger.error("TokenManager setTokenData error",e);
            throw new BusinessException(EnumCode.PARAMS_ERROR, "设置token失败");
        }
        return token;
    }

    /**
     * 获取tokenValue
     * @param sysCode
     * @param token
     * @param valid 是否校验过期，如校验则校验是否有效 有效的话 增加时效 返回data 无效的话 返回空 不校验 直接返回data
     * @return
     */
    public String getTokenData(String sysCode, String token, boolean valid){
        String data = null;
        try {

            Custom custom = new Custom(tokenConfig.getTokenCustName(), tokenConfig.getTokenCustPass());
            String accessKey = custom.getAccessKey();
            String decryptToken = DESUtil.getInstance(accessKey).decrypt(token);
            int milliLength = String.valueOf(System.currentTimeMillis()).length();
            int tokenLength = String.valueOf(System.currentTimeMillis()).length()+accessKey.length()+1;
            if(StrUtil.isBlank(decryptToken) || decryptToken.length() < tokenLength){
                return null;
            }

            String timestamp = decryptToken.substring(0,milliLength);
            String tokenAccess = decryptToken.substring(milliLength,tokenLength-1);
            String tokenData = decryptToken.substring(tokenLength-1);
            boolean match = ReUtil.isMatch("\\d+", timestamp);
            if(!match || !accessKey.equals(tokenAccess)){
                return null;
            }

            if(!valid){
                return tokenData;
            }

            String redisKey = sysCode+TokenRedis.REDIS_KEY_TOKEN;
            String redisValue = (String) redisUtils.hget(redisKey, token);
            if(StrUtil.isBlank(redisValue)){
                return null;
            }

            long startTimeStamp = Long.parseLong(timestamp);
            String endTimeStamp = DESUtil.getInstance(accessKey).decrypt(redisValue);
            if(StrUtil.isBlank(endTimeStamp) || !ReUtil.isMatch("\\d+", endTimeStamp)
                    || endTimeStamp.length() < String.valueOf(System.currentTimeMillis()).length()+1){
                return null;
            }

            long endTime = Long.parseLong(endTimeStamp.substring(0,milliLength));
            long expireSeconds = Long.parseLong(endTimeStamp.substring(milliLength));

            TokenRedis tokenRedis = new TokenRedis();
            tokenRedis.setExpireTime(expireSeconds*1000);
            tokenRedis.setAccessKey(accessKey);
            tokenRedis.setTokenValue(tokenData);
            tokenRedis.setToken(token);
            tokenRedis.setStartTime(startTimeStamp);
            tokenRedis.setEndTime(endTime);
            data = tokenRedis.getData(valid);
            if(StrUtil.isBlank(data)){
                redisUtils.hdel(redisKey,token);
                return null;
            }

            String redisVal = DESUtil.getInstance(accessKey).encrypt(String.valueOf(tokenRedis.getEndTime())
                    +String.valueOf(expireSeconds));
            redisUtils.hset(redisKey, token,redisVal,expireSeconds);
        }catch (Exception e){
            logger.error("TokenManager getTokenData error",e);
        }
        return data;
    }

    /**
     * 获取tokenValue
     * @param token
     * @param custName
     * @param custPass
     * @return
     */
    public String getTokenData(String token,String custName,String custPass){
        if(StrUtil.isBlank(custName) || StrUtil.isBlank(custPass)){
            return null;
        }
        Custom custom = new Custom(custName,custPass);
        String accessKey = custom.getAccessKey();

        try {
            String decryptToken = DESUtil.getInstance(accessKey).decrypt(token);
            int milliLength = String.valueOf(System.currentTimeMillis()).length();
            int tokenLength = String.valueOf(System.currentTimeMillis()).length()+accessKey.length()+1;
            if(StrUtil.isBlank(decryptToken) || decryptToken.length() < tokenLength){
                return null;
            }

            String timestamp = decryptToken.substring(0,milliLength);
            String tokenAccess = decryptToken.substring(milliLength,tokenLength-1);
            String tokenData = decryptToken.substring(tokenLength-1);
            boolean match = ReUtil.isMatch("\\d+", timestamp);
            if(!match || !accessKey.equals(tokenAccess)){
                return null;
            }

            return tokenData;
        }catch (Exception e){
            logger.error("TokenManager getTokenData error",e);
        }
        return null;
    }

    /**
     * 清除token
     * @param token
     * @return
     */
    public void clearToken(String sysCode, String token){
        String redisKey = sysCode+TokenRedis.REDIS_KEY_TOKEN;
        redisUtils.hdel(redisKey,token);
        clearUserToken(sysCode,"",token);
    }

    /**
     * 清除
     * @param sysCode
     * @param userId 可为空 为空时 从token获取用户ID
     * @param token 为空时 清除用户所有token
     */
    private void clearUserToken(String sysCode,String userId,String token){
        if(StrUtil.isNotBlank(token)){
            if(StrUtil.isBlank(userId)){
                userId = getTokenData(sysCode, token,false);
            }
            if(StrUtil.isNotBlank(userId)){
                clearUserTokenItem(sysCode,userId,token);
            }
        }else{
            if(StrUtil.isNotBlank(userId)){
                clearUserToken(sysCode,userId);
            }
        }
    }

    /**
     * 清除token
     * @param sysCode
     * @param userId
     */
    private void clearUserToken(String sysCode, String userId){
        String redisKey = sysCode+ UserToken.REDIS_KEY_TOKEN;
        redisUtils.hdel(redisKey,userId);
    }

    /**
     * 清除某个用户所有token
     * @param sysCode
     * @param userId
     */
    public void clearUserAllToken(String sysCode, String userId){
        String redisKey = sysCode+ UserToken.REDIS_KEY_TOKEN;
        List<UserToken> userTokens = getUserTokens(sysCode, userId);
        if (ObjectUtil.isNotEmpty(userTokens)) {
            for (UserToken userToken:userTokens){
                if(StrUtil.isNotBlank(userToken.getToken())){
                    String tokenKey = sysCode+TokenRedis.REDIS_KEY_TOKEN;
                    redisUtils.hdel(tokenKey,userToken.getToken());
                }
            }
        }
        redisUtils.hdel(redisKey,userId);
    }

    /**
     * 清除token
     * @param sysCode
     * @param userId
     * @param token
     */
    private void clearUserTokenItem(String sysCode, String userId,String token){
        String redisKey = sysCode+ UserToken.REDIS_KEY_TOKEN;
        List<UserToken> userTokens = getUserTokens(sysCode, userId);
        if(ObjectUtil.isNotEmpty(userTokens)){
            Iterator<UserToken> iterator = userTokens.iterator();
            while (iterator.hasNext()){
                if(token.equals(iterator.next().getToken())){
                    iterator.remove();
                    break;
                }
            }
        }else {
            redisUtils.hdel(redisKey,userId);
        }
    }

    /**
     * 设置用户token
     * @param sysCode
     * @param userId
     * @param token
     */
    private void setUserToken(String sysCode, String userId,String token){
        String redisKey = sysCode+ UserToken.REDIS_KEY_TOKEN;
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String host = IpUtil.getIpAddress(request)+":"+request.getRemotePort();
        UserToken userToken = new UserToken();
        userToken.setHost(host);
        userToken.setToken(token);

        boolean flag = false;
        List<UserToken> userTokens = getUserTokens(sysCode, userId);
        if(ObjectUtil.isNotEmpty(userTokens)){
            if (!isMultiFlag()){
                Iterator<UserToken> iterator = userTokens.iterator();
                while (iterator.hasNext()){
                    UserToken next = iterator.next();
                    if(StrUtil.isNotBlank(next.getHost()) && StrUtil.isNotBlank(userToken.getHost())){
                        redisUtils.hdel(sysCode+TokenRedis.REDIS_KEY_TOKEN,next.getToken());
                        iterator.remove();
                    }
                }
            }
            userTokens.add(userToken);
            redisUtils.hset(redisKey,userId, JSON.toJSONString(userTokens));
            flag = true;
        }

        if(flag == false){
            List<UserToken> list = new ArrayList<>();
            list.add(userToken);
            redisUtils.hset(redisKey,userId, JSON.toJSONString(list));
        }
    }

    /**
     * 获取用户所有token
     * @param sysCode
     * @param userId
     * @return
     */
    public List<UserToken> getUserTokens(String sysCode,String userId){
        String redisKey = sysCode+ UserToken.REDIS_KEY_TOKEN;
        String tokens = (String) redisUtils.hget(redisKey,userId);
        if(StrUtil.isNotBlank(tokens)) {
            List<UserToken> list = JSON.parseArray(tokens, UserToken.class);
            return list;
        }
        return null;
    }
}
